<?php
/**
 * This file defines the core of
 * @package contenite
 */
require_once 'controller.php';
require_once 'model.php';
require_once 'db.php';

/**
 * Set (and get) the current controller
 */
function set_controller($controller = NULL) {
  static $_controller;
  if (!isset($_controller)) {
    $_controller = $controller;
  }
  return $_controller;
}

function get_controller() {
  return set_controller();
}

/**
 * Output data from the current controller
 * @param $names an array of field names
 * @param $options with the following keys
 * - print - print the values in addition to returning them
 * - separator - separate the field values by this string
 * - format - a printf format; takes precedence over just concatinating the values
 */
function contenite($names, $options = array()) {
  $options += array('print' => TRUE, 'separator' => ' ', 'nl2br' => TRUE);
  
  $controller = get_controller();
  if (!is_array($names)) {
    $names = array($names);
  }
  
  $output = array();
  foreach ($names as $name) {
    if (isset($controller->data->_fields[$name]['widget'])) {
      $widget = $controller->data->_fields[$name]['widget'];
    }
    else
    if (strcasecmp($controller->data->_fields[$name]['type'], 'DATE') === 0) {
      $widget = 'date'; 
    }
    else {
      $widget = 'text';
    }
      
    if (is_array($widget)) {
      $widget_name = array_shift($widget);
    }
    else {
      $widget_name = $widget;
      $widget = array();
    }
    
    $class = 'cte-' . get_class($controller->data) . '-' . $name;
    switch ($widget_name) {
      case 'image':
        $size = '';
        if (isset($options['width'])) {
          $size .= $options['width'];
        }
        if (isset($options['height'])) {
          $size .= 'x' . $options['height'];
        }
        if ($size) {
          $size .= '.';
        }
        if (isset($controller->data->$name)) {
          $path = pathinfo($controller->data->$name);
        }
        else {
          $path = array(
            'dirname' => '',
            'basename' => '');
        }
        $file = '/files/'
          . $controller->data->{$controller->data->_key} . '/' 
          . $path['dirname'] . ($path['dirname'] ? '/' : '') 
          . $size . $path['basename'];

        $output[] = '<img class="' . $class . '" src="' 
          . $file . '" />';
        break;

      case 'file':
        $output[] = '<a class="' . $class . '" href="'
          . $controller->data->{$controller->data->_key}
          . '/'. $controller->data->$name . '">Anhang</a>';
        break;
      
      case 'many':
        $def =& $controller->data->_has_many[$name];
        
        if (isset($def['item'])) {
          $output[] = $controller->_render($def['item'], array(
            'name' => $name,
            'def' => $controller->data->_has_many[$name],
            'slots' => $controller->data->$name));
        }
        else {
          error_log('contenite: "many" needs formatting item');
        }
        break;
      
      default:
        $value = isset($controller->data->$name)
          ? $controller->data->$name : '';
        if (isset($controller->data->_fields[$name]['default'])
            && $value == $controller->data->_fields[$name]['default']) {
          $class .= ' empty';
        }
        if (isset($options['raw']) && $options['raw']) {
          $output[] = $value;
        }
        else {
          if ($widget_name == 'date' && $value == '0000-00-00') {
            $value = 'heute';
          }
          $output[] = '<span title="Zum Bearbeiten hier klicken" class="' . $class . ' ' 
            . (isset($controller->data->_fields[$name]['display class']) 
                ? $controller->data->_fields[$name]['display class'] 
                : '') . '">' . nl2br($value) . '</span>';
        }
    }
  }
  if (isset($options['format'])) {
    $output = vsprintf($options['format'], $output);
  }
  else {
    $output = join($options['separator'], $output);
  }
  if ($options['print']) {
    echo $output;
  }
  return $output;
}

function element($name, $vars = NULL) {
  $controller = get_controller();
  return $controller->_render('elements/' . $name, $vars);
}

function message($msg = NULL, $format = NULL) {
  if ($msg !== NULL) {
    $_SESSION['message'][] = $msg;
  }
  else
  if (isset($_SESSION['message'])) {
    $msg =  is_array($_SESSION['message']) ? $_SESSION['message'] : array($_SESSION['message']);
    if ($format) {
      echo strtr($format, array('%message' => join('<br/>', $msg)));
    }
    else {
      echo join('<br/>', $msg);
    }
    unset($_SESSION['message']);
  }
  return $msg;
}

/**
 * Helper for forms. Check if the given field is set in session and return it. 
 */
function data($field, $default = '') {
  return isset($_POST[$field])
    ? $_POST[$field]
    : (isset($_SESSION['data'][$field]) 
       ? $_SESSION['data'][$field] 
       : $default);
}

function redirect($url = NULL, $destination = NULL) {
  global $config;

  if (!$url || strpos($url, '#') === 0) {
    if (isset($_SESSION['destination'])) {
      $url = $_SESSION['destination'] . ($url ? $url : '');
    }
    else {
      $url = $config['root'] . ($url ? $url : '');
    }
  }
  if ($destination) {
    $url .= (strpos($url, '?') === FALSE ? '?' : '&') 
      . 'destination=' . urlencode($destination);
  }
  if (!$url) {
    $url = '/';
  }
  error_log("REDIR -> $url (destination is {$destination})");
  header('Location: ' . $url);
  exit();
}

/**
 * Load a file in the app sub-directory and instantiate its contained class
 */
function load($name, $type = 'model') {
  global $config;
  
  @include_once 'app.c.php';
  @include_once 'app.m.php';

  if (!class_exists('app_model')) {
    class app_model extends model { function access() { return TRUE; } }
  }

  if (!class_exists('app_controller')) {
    class app_controller extends controller {}      
  }

  $suffix = '.' . $type{0} . '.php';
  require_once $name . $suffix;
  if ($type == 'model') {
    $name .= '_generator';

    /*
     * Usually, this class does not contain app specific stuff so we 
     * provide an empty implementation here
     */
    if (!class_exists($name)) {
      eval('class ' . $name . ' extends model_generator {}');
    }
  }
  else {
    $name .= '_' . $type;
  }
  return new $name;
}

function routes($app) {
  @include $app . '/' . 'routes.php';
  return isset($routes) ? $routes : array();
}

/**
 * A simple router that gets routes from the global hash $config[routes]
 * XXX explain the auto loading of arguments
 * The values define routes and are again hashes 
 * with the keys 'controller', 'action', and - optionally - 'auth'.
 * - CONTROLLER is the name of a file in sup-directory 'app' CONTROLLER.c.php
 *   this file should contain a class CONTROLLER_controller
 * - ACTION is the name of a method of this class
 * - AUTH is the name prefix of a controller that is able to authenticate users
 */
function route($q) {
  global $config;

  $path = array_filter(explode('/', $q), 'trim'); // split and remove empty components

  /*
   * Check for "stacked" apps and split off app directory from front of $q
   */
  $app = 'app';
  for ($i = 0; $i < count($path); $i++) {
    if (is_dir($checked = $app . '/' . $path[$i])) {
      $app = $checked;
    }
    else {
      break;
    }
  }

  $config['app_dir'] = $app;

  if ($app != 'app') {
    $path = array_slice($path, $i);
  }
  $q = join('/', $path);
  
  $args = array();
  foreach (routes($app) as $pattern => $route) {
    if (preg_match('#^' . $pattern . '$#', $q, $matches)) {
      foreach (array_slice($matches, 1) as $i => $match) {
        if (isset($route['args'][$i]) && is_string($route['args'][$i])) {
          $generator = load($route['args'][$i]);
          $args[] = $generator->read($match);
        }
        else {
          $args[] = $match;
        }
      } 
      if (isset($route['auth'])) {
        $auth = load($route['auth'], 'controller');
        if (!$auth->valid()) {
          redirect($route['auth'] . '/login', $q);
        }
      }
      $controller_name = $route['controller'];
      $action = $route['action'];
      
      break;
    }
  }
  
  if (!isset($action)) {
    /*
     * No route found. Fill in default path components.
     * If only a controller is given, add action='index'.
     * If neither controller not action is given, add controller='index' and action='index'
     */
    while (count($path) < 2) {
      array_push($path, 'index');
    }

    $controller_name = $path[0];
    $action = $path[1];

    $args = array_slice($path, 2);
  }
  
  $controller = load($controller_name, 'controller');
  $controller->_act($action, $path, $args);
}

function vars($attr) {
  $output = '';
  $sep = '';
  foreach ($attr as $n => $v) {
    $output .= $sep . $n . '="' . addslashes($v) . '"';
    $sep = ' ';
  }
  return $output;
}

function bt() {
  $bt = debug_backtrace();
  array_shift($bt);
  $output = '';
  foreach ($bt as $frame) {
    if (isset($frame['file'])) {
      $output .= sprintf("%s:%d %s()<br/>\n", 
        $frame['file'], $frame['line'], $frame['function']);
    }
    else {
      $output .= sprintf("%s::%s()<br/>\n", 
        $frame['class'], $frame['function']);
    }
  }
  return $output;
}

function send_mail($to, $subject, $message, $auto_link = TRUE) {
  if ($auto_link) {
    $message = preg_replace('#http://(\S+)#', '<a href="$1">$1</a>', $message);
  } 
  mail($to, $subject, 
      '<html>' . nl2br($message) . '</html>',
      "MIME-Version: 1.0\n"
    . "Content-Type: text/html; charset=UTF-8\n"
    . "Content-Transfer-Encoding: 8bit\n"
    . "From: CV Aid <info@cv8.me>\n");
}

function gen_password() {
  return base_convert(rand(), 10, 36);
}

function truncate($text, $len = 60) {
  $lines = preg_split("/\n/", wordwrap($text, $len));
  return $lines[0] . (strlen($lines[0]) < strlen($text) ? '...' : '');
}

function tsfile($path) {
  $ts = file_exists($path) ? filemtime($path) : '';
  return $path . ($ts ? "?$ts" : '');
}

function __($string, $args = array(), $context = '*') {
  global $config;
  
  static $lang;
  if (!isset($lang)) {
    @include 'lang_' . $config['lang'] . '.php';
  }
  
  if ($context == '*' && isset($lang[$string])) {
    $string = $lang[$string];
  }
  else
  if (isset($lang[$context][$string])) {
    $string = $lang[$context][$string];
  }
  return strtr($string, $args);
}

/**
 * Register stacked apps in the include path
 * so that we may find files from any app later
 */
function apps($dir = 'app') {
  global $config;
  
  $app = $config['root_dir'] . '/' . $dir;
  set_include_path($app . PATH_SEPARATOR . get_include_path());

  $d = opendir($app);
  while ($entry = readdir($d)) {
    if (strpos($entry, '.') === 0) {
      continue;
    }
    $path = $app . '/' . $entry;
    if (!is_dir($path)) {
      continue;
    }
    apps($dir . '/' . $entry);
  }
  closedir($d);
}

/**
 * Render a hierarchical array into a hierarchy of unordered lists
 * example: 
 * array(
 *   '#url' => '/',
 *   '#title' => 'Home',
 *   'menu1' => array(
 *     '#url' => 'menu1', 
 *     '#title' => 'Menu 1', 
 *    ),
 *   'menu2' => array(
 *   ),
 * )
 */
function menu($menu, $level = 0) {
  $result = '';
  $s = str_repeat('  ', $level);
  if (isset($menu['#path'])) {
    $result .= sprintf("%s<li><a href=\"/%s\">%s</a></li>\n", $s, $menu['#path'], $menu['#title']);
  }
  $result .= $s . "<ul>\n";
  foreach ($menu as $name => $m) {
    if (strpos($name, '#') === 0) {
      continue;
    }
    $result .= menu($m, $level + 1);
  }
  $result .= "</ul>\n";
  return $result;
}
